package jxtend.processor.eclipse.visitor;

import java.util.List;

import jxtend.processor.eclipse.visitor.handler.ASTNodeVisitorContext;
import jxtend.processor.eclipse.visitor.interceptor.ASTNodeVisitorInterceptor;
import jxtend.processor.eclipse.visitor.interceptor.ASTNodeVisitorInterceptorContext;

import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.lookup.Scope;

@SuppressWarnings("restriction")
public class InterceptedASTNodeVisitor extends ASTNodeVisitorWrapper {

	private final List<ASTNodeVisitorInterceptor> interceptors;
	private final ASTVisitor astVisitor;

	public InterceptedASTNodeVisitor(
			ASTNodeVisitor target, List<ASTNode> parentNodes, List<ASTNodeVisitorInterceptor> interceptors) {
		super(target);
		this.interceptors = interceptors;
		this.astVisitor = ASTVisitorFactory.create(this, parentNodes);
	}

	public ASTVisitor getASTVisitor() {
		return astVisitor;
	}

	@Override
	public boolean visit(ASTNodeVisitorContext context, ASTNode node, Scope scope) {
		if (interceptors.isEmpty()) {
			return super.visit(context, node, scope);
		}

		return interceptors.get(0).interceptVisit(new InterceptorChain(astVisitor, context, false, 0), node, scope);
	}

	private boolean superVisit(ASTNodeVisitorContext context, ASTNode node, Scope scope) {
		return super.visit(context, node, scope);
	}

	@Override
	public void endVisit(ASTNodeVisitorContext context, ASTNode node, Scope scope) {
		if (interceptors.isEmpty()) {
			super.endVisit(context, node, scope);
			return;
		}

		interceptors.get(0).interceptEndVisit(new InterceptorChain(astVisitor, context, true, 0), node, scope);
	}

	private void superEndVisit(ASTNodeVisitorContext context, ASTNode node, Scope scope) {
		super.endVisit(context, node, scope);
	}

	private class InterceptorChain implements ASTNodeVisitorInterceptorContext {

		private final ASTVisitor astVisitor;
		private final ASTNodeVisitorContext context;
		private boolean doingEndVisit;
		private int index;
		
		public InterceptorChain(
				ASTVisitor astVisitor, ASTNodeVisitorContext context, boolean doingEndVisit, int index) {
			super();
			this.astVisitor = astVisitor;
			this.context = context;
			this.doingEndVisit = doingEndVisit;
			this.index = index;
		}

		public ASTVisitor getASTVisitor() {
			return astVisitor;
		}

		@Override
		public boolean doVisit(ASTNode node, Scope scope) {
			if (doingEndVisit) {
				throw new IllegalStateException("The 'doVisit' method must only be called during an 'interceptVisit'.");
			}

			if (index < (interceptors.size() - 1)) {
				index++;
				final ASTNodeVisitorInterceptor nextInterceptor = interceptors.get(index);

				return nextInterceptor.interceptVisit(this, node, scope);
			} else {
				return InterceptedASTNodeVisitor.this.superVisit(context, node, scope);
			}
		}

		@Override
		public void doEndVisit(ASTNode node, Scope scope) {
			if (!doingEndVisit) {
				throw new IllegalStateException("The 'doVisit' method must only be called during an 'interceptVisit'.");
			}

			if (index < (interceptors.size() - 1)) {
				index++;
				final ASTNodeVisitorInterceptor nextInterceptor = interceptors.get(index);

				nextInterceptor.interceptEndVisit(this, node, scope);
			} else {
				InterceptedASTNodeVisitor.this.superEndVisit(context, node, scope);
			}
		}

		@Override
		public ASTNode getParentNode() {
			return context.getParentNode();
		}

		@Override
		public ASTNode getParentNode(int index) {
			return context.getParentNode(index);
		}

		@Override
		public <A extends ASTNode> A findParentNode(Class<A> parentNodeType) {
			return context.findParentNode(parentNodeType);
		}
	}
}
